/*NOTICE

This software was produced for the U. S. Government
under Contract No. FA8721-09-C-0002, and is
subject to the Rights in Noncommercial Computer Software
and Noncommercial Computer Software Documentation Clause
(DFARS) 252.227-7014 (JUN 1995)

(c) 2009 The MITRE Corporation. All Rights Reserved.
*/
package org.mitre.xmltransform.xsdcomplextype2txt;

import java.io.File;

import org.mitre.xmltransform.transform.Transform;
import org.mitre.xmltransform.txt.TxtOutputDocument;
import org.mitre.xmltransform.xsd.XsdInputDocument;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XsdComplexType2TxtTransform extends Transform {

	@Override
	public void initialize(File pInputFile, File pOutputFile) {
		mInput = new XsdInputDocument(pInputFile);
		mOutput = new TxtOutputDocument(pOutputFile);
	}
	
	public int setArguments(String[] pTransformArguments) {
		// expects 1 argument
		if (pTransformArguments == null || pTransformArguments.length != 1) {
			return SETARGS_BAD_ARGUMENT_COUNT;
		}
		mComplexTypeName = pTransformArguments[0];
		return SETARGS_NO_ERROR;		
	}

	public String getUsageMessage() {
		String result = super.getUsageMessage();
		result += " < complex type name >";
		return result;
	}

	@Override
	public void transform() {
		if (generateOutput()) {
			mOutput.finishDocument();
			mOutput.writeOutput();
		}		
	}

	private String buildAttributeName(String pBaseItem, String pPrefix, String pName, String pMinOccurs, String pMaxOccurs) {
		String result;
		if (pBaseItem.length() == 0) result = pPrefix + pName;
		else result = pBaseItem + ATTRIBUTE_DELIMITER + pPrefix + pName;

		if (pMinOccurs == null || pMinOccurs.length() == 0) pMinOccurs="1";
		if (pMaxOccurs == null || pMaxOccurs.length() == 0) pMaxOccurs="1";
		if (pMinOccurs.equals("1") && pMaxOccurs.equals("1")) return result;
		if (pMaxOccurs.equalsIgnoreCase("unbounded")) pMaxOccurs = "*";
		result = result + "[" + pMinOccurs + ":" + pMaxOccurs + "]";
		return result;
	}

	private String getSimpleTypeAsString(Node pSimpleTypeNode) {
		String base = mInput.getInputValue(pSimpleTypeNode, "xs:restriction/@base");
		int colonPos = base.indexOf(":");
		if (colonPos != -1) {
			base = base.substring(colonPos + 1);
		}
		if (base.equals("string")) {
			String minLength = mInput.getInputValue(pSimpleTypeNode, "xs:restriction/xs:minLength/@value");
			if (minLength != null && minLength.length() == 0) {
				minLength = null;
			}
			String maxLength = mInput.getInputValue(pSimpleTypeNode, "xs:restriction/xs:maxLength/@value");
			if (maxLength != null && maxLength.length() == 0) {
				maxLength = null;
			}
			if (minLength == null && maxLength == null) {
				NodeList enumerations = mInput.getInputNodes(pSimpleTypeNode, "xs:restriction/xs:enumeration");
				if (enumerations.getLength() != 0) {
					StringBuffer result = new StringBuffer(base);
					Node enumItem;
					
					result.append("[");
					for (int i = 0; i < enumerations.getLength(); i++) {
						enumItem = enumerations.item(i);
						if (i > 0) {
							result.append(" | ");
						}
						result.append(mInput.getInputValue(enumItem, "@value"));
					}
					result.append("]");
					
					return result.toString();
				}
				String pattern = mInput.getInputValue(pSimpleTypeNode, "xs:restriction/xs:pattern/@value");
				if (pattern != null && pattern.length() > 0) {
					return base + "[" + pattern + "]";
				}
				return base;
			} else if (minLength == null && maxLength != null) {
				return base + "[0:" + maxLength + "]";
			} else if (minLength != null && maxLength == null) {
				return base + "[" + minLength + ":...]";
			} else {
				return base + "[" + minLength + ":" + maxLength + "]";
			}
		} else if (base.equals("int") || base.equals("double") 
				|| base.equals("integer") || base.equals("decimal")) {
			
			// TO DO: support xs:fractionDigits for xs:decimal
			String minValue = mInput.getInputValue(pSimpleTypeNode, "xs:restriction/xs:minInclusive/@value");
			if (minValue != null && minValue.length() == 0) {
				minValue = null;
			}
			String maxValue = mInput.getInputValue(pSimpleTypeNode, "xs:restriction/xs:maxInclusive/@value");
			if (maxValue != null && maxValue.length() == 0) {
				maxValue = null;
			}
			if (minValue == null && maxValue == null) {
				return base;
			} else if (minValue == null && maxValue != null) {
				return base + "[0:" + maxValue + "]";
			} else if (minValue != null && maxValue == null) {
				return base + "[" + minValue + ":...]";
			} else {
				return base + "[" + minValue + ":" + maxValue + "]";
			}
			
		} else {
			String baseNoPrefix;
			colonPos = base.indexOf(":");
			if (colonPos == -1) {
				baseNoPrefix = base; 
			} else {
				baseNoPrefix = base.substring(colonPos + 1);
			}
			Node baseSimpleTypeNode = mInput.getInputNode("//xs:simpleType[@name='" + baseNoPrefix + "']");
			if (baseSimpleTypeNode != null) {
				return getSimpleTypeAsString(baseSimpleTypeNode);
			}
			return base;
		}
	}
	
	private void outputItems(Node pContext, String pBaseItem) {
		Node curNode, typeNode;
		String baseComplexTypeName;
		String elementName, elementType, minOccurs, maxOccurs, refElement;
		String attributeName, prefix;
		NodeList subelements;
		int colonPos;

		if (mInput.getInputNode(pContext, "xs:sequence") != null) {
			// sequence
			subelements = mInput.getInputNodes(pContext, "xs:sequence/xs:element");
			prefix = "";
		} else if (mInput.getInputNode(pContext, "xs:choice") != null) {
			// choice
			prefix = "(C)";
			subelements = mInput.getInputNodes(pContext, "xs:choice/xs:element");
		} else if (mInput.getInputNode(pContext, "xs:complexContent/xs:extension") != null) {
			// extension elements
			baseComplexTypeName = mInput.getInputValue(pContext, "xs:complexContent/xs:extension/@base");
			
			Node baseComplexType = mInput.getTypeNode(pContext, baseComplexTypeName);
			
			if (baseComplexType == null) {
				throw new IllegalArgumentException("Base complex type " + baseComplexTypeName + " not found.");
			}
			outputItems(baseComplexType, pBaseItem); // use base complex type with current base item
			// now the sub-type specific items
			Node extensionBase = mInput.getInputNode(pContext, "xs:complexContent/xs:extension");
			if (extensionBase == null) {
				throw new IllegalArgumentException("no extension found!");
			}
			outputItems(extensionBase, pBaseItem);
			return;
		} else if (mInput.getInputNodes(pContext, "xs:attribute") != null) {
			prefix = "";
			subelements = mInput.getInputNodes(pContext, "xs:attribute");
		} else {
			throw new IllegalArgumentException("Unable to interpret " + pContext);
		}
		
		for (int i = 0; i < subelements.getLength(); i++) {
			curNode = subelements.item(i);
			minOccurs = mInput.getInputValue(curNode, "@minOccurs");
			maxOccurs = mInput.getInputValue(curNode, "@maxOccurs");
			elementName = mInput.getInputValue(curNode, "@name");
			if (elementName == null || elementName.length() == 0) {
				refElement = mInput.getInputValue(curNode, "@ref");
				colonPos = refElement.indexOf(":");
				if (colonPos != -1) {
					refElement = refElement.substring(colonPos + 1);
				}
				curNode = mInput.getInputNode("//xs:element[@name='" + refElement + "']");
				if (curNode == null) {
					throw new IllegalArgumentException("Unable to find referenced element: " + refElement);
				}
				elementName = mInput.getInputValue(curNode, "@name");
			}
			elementType = mInput.getInputValue(curNode, "@type");
			attributeName = buildAttributeName(pBaseItem, prefix, elementName, minOccurs, maxOccurs);

			if (elementType == null || elementType.length() == 0) {
				// embedded type info
				outputItems(mInput.getInputNode(curNode, "xs:complexType"), attributeName);
			} else {
				typeNode = mInput.getType(elementType);
				if (typeNode == null) {
					mOutput.addRecord(new String[] { attributeName, elementType });
				} else if (mInput.isTypeNodeComplex(typeNode)) {
					outputItems(typeNode, attributeName);
				} else {
					mOutput.addRecord(new String[] { attributeName, getSimpleTypeAsString(typeNode) });
				}
			}
		}
	}
	
	private boolean generateOutput() {
		//try {
			Node theComplexType = mInput.getInputNode("//xs:complexType[@name='" + mComplexTypeName + "']");
			
			outputItems(theComplexType, "");
			
			return true;
		/*} catch (Exception e) {
			e.printStackTrace();
			return false;
		}*/
	}
	
	private static final String ATTRIBUTE_DELIMITER = ".";
	
	private String mComplexTypeName;
	private XsdInputDocument mInput;
	private TxtOutputDocument mOutput;
}
